<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>

<body>
  <canvas></canvas>
  <div>
    <button onclick="cancel()">取消</button>
    <button onclick="save()">保存</button>
  </div>
</body>
<script>
  // 配置内容
  const config = {
    width: 400, // 宽度
    height: 200, // 高度
    lineWidth: 5, // 线宽
    strokeStyle: 'black', // 线条颜色
    lineCap: 'round', // 设置线条两端圆角
    lineJoin: 'round', // 线条交汇处圆角
  }

  // 获取canvas 实例
  const canvas = document.querySelector('canvas')
  // 设置宽高
  canvas.width = config.width
  canvas.height = config.height
  // 设置一个边框
  canvas.style.border = '1px solid #000'
  // 创建上下文
  const ctx = canvas.getContext('2d')

  // 设置填充背景色
  ctx.fillStyle = 'transparent'
  // 绘制填充矩形
  ctx.fillRect(
    0, // x 轴起始绘制位置
    0, // y 轴起始绘制位置
    config.width, // 宽度
    config.height // 高度
  );

  // 保存上次绘制的 坐标及偏移量
  const client = {
    offsetX: 0, // 偏移量
    offsetY: 0,
    endX: 0, // 坐标
    endY: 0
  }

  // 判断是否为移动端
  const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent))

  // 初始化
  const init = event => {
    // 获取偏移量及坐标
    const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event

    // 修改上次的偏移量及坐标
    client.offsetX = offsetX
    client.offsetY = offsetY
    client.endX = pageX
    client.endY = pageY

    // 清除以上一次 beginPath 之后的所有路径，进行绘制
    ctx.beginPath()
    // 根据配置文件设置相应配置
    ctx.lineWidth = config.lineWidth
    ctx.strokeStyle = config.strokeStyle
    ctx.lineCap = config.lineCap
    ctx.lineJoin = config.lineJoin
    // 设置画线起始点位
    ctx.moveTo(client.endX, client.endY)
    // 监听 鼠标移动或手势移动
    window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw)
  }
  // 绘制
  const draw = event => {
    // 获取当前坐标点位
    const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
    // 修改最后一次绘制的坐标点
    client.endX = pageX
    client.endY = pageY

    // 根据坐标点位移动添加线条
    ctx.lineTo(pageX, pageY)

    // 绘制
    ctx.stroke()
  }
  // 结束绘制
  const closeDraw = () => {
    // 结束绘制
    ctx.closePath()
    // 移除鼠标移动或手势移动监听器
    window.removeEventListener("mousemove", draw)
  }
  // 创建鼠标/手势按下监听器
  window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init)
  // 创建鼠标/手势 弹起/离开 监听器
  window.addEventListener(mobileStatus ? "touchend" : "mouseup", closeDraw)

  // 取消-清空画布
  const cancel = () => {
    // 清空当前画布上的所有绘制内容
    ctx.clearRect(0, 0, config.width, config.height)
  }
  // 保存-将画布内容保存为图片
  const save = () => {
    // 将canvas上的内容转成blob流
    canvas.toBlob(blob => {
      // 获取当前时间并转成字符串，用来当做文件名
      const date = Date.now().toString()
      // 创建一个 a 标签
      const a = document.createElement('a')
      // 设置 a 标签的下载文件名
      a.download = `${date}.png`
      // 设置 a 标签的跳转路径为 文件流地址
      a.href = URL.createObjectURL(blob)
      // 手动触发 a 标签的点击事件
      a.click()
      // 移除 a 标签
      a.remove()
    })
  }
</script>

</html>